//-----------------------------------------------------------------------
// <copyright file="ComHelper.cs" company="Unknown">
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY 
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
// This code released under the terms of the 
// Microsoft Public License (MS-PL, http://opensource.org/licenses/ms-pl.html.)
// </copyright>
// <date>16-May-2011</date>
//-----------------------------------------------------------------------

namespace SmartTag.Common.IFilter
{
    using System;
    using System.Collections.Generic;
    using System.Runtime.InteropServices;
    using SmartTag.Common.Utility;

    /// <summary>
    /// Enables a class of objects to be created.
    /// </summary>
    [ComVisible(false)]
    [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("00000001-0000-0000-C000-000000000046")]
    internal interface IClassFactory
    {
        /// <summary>
        /// Creates an uninitialized object.
        /// </summary>
        /// <param name="outerIUnknown">
        /// If the object is being created as part of an aggregate, specify a pointer to the controlling IUnknown
        /// interface of the aggregate. Otherwise, this parameter must be NULL.
        /// </param>
        /// <param name="refiid">
        /// A reference to the identifier of the interface to be used to communicate with the newly created object.
        /// If pUnkOuter is NULL, this parameter is generally the IID of the initializing interface; if pUnkOuter is non-NULL, riid must be IID_IUnknown.
        /// </param>
        /// <param name="unknownInterface">
        /// The address of pointer variable that receives the interface pointer requested in riid.
        /// Upon successful return, *ppvObject contains the requested interface pointer.
        /// If the object does not support the interface specified in riid, the implementation must set *ppvObject to NULL.
        /// </param>
        void CreateInstance([MarshalAs(UnmanagedType.Interface)] object outerIUnknown, ref Guid refiid, [MarshalAs(UnmanagedType.Interface)] out object unknownInterface);

        /// <summary>
        /// Locks an object application open in memory. This enables instances to be created more quickly.
        /// </summary>
        /// <param name="incrementLock">If TRUE, increments the lock count; if FALSE, decrements the lock count.</param>
        void LockServer(bool incrementLock);
    }

    /// <summary>
    /// Utility class to get a Class Factory for a certain Class ID 
    /// by loading the dll that implements that class
    /// </summary>
    internal static class ComHelper
    {
        /// <summary>
        /// List of dlls
        /// </summary>
        private static DllList dllList = new DllList();

        /// <summary>
        /// Retrieves the class object from a DLL object handler or object application.
        /// </summary>
        /// <param name="clsId">The CLSID that will associate the correct data and code.</param>
        /// <param name="interfaceId">
        /// A reference to the identifier of the interface that the caller is to use to communicate with the class object.
        /// Usually, this is IID_IClassFactory (defined in the OLE headers as the interface identifier for IClassFactory).
        /// </param>
        /// <param name="ppv">
        /// The address of a pointer variable that receives the interface pointer requested in riid.
        /// Upon successful return, *ppv contains the requested interface pointer. If an error occurs, the interface pointer is NULL. 
        /// </param>
        /// <returns>
        /// This function can return the standard return values E_INVALIDARG, E_OUTOFMEMORY, and E_UNEXPECTED, as well as the following values.
        /// S_OK -                          The object was retrieved successfully.
        /// CLASS_E_CLASSNOTAVAILABLE -     The DLL does not support the class (object definition).
        /// </returns>
        private delegate int DllGetClassObject(ref Guid clsId, ref Guid interfaceId, [Out, MarshalAs(UnmanagedType.Interface)] out object ppv);

        /// <summary>
        /// Gets a class factory for a specific COM Class ID. 
        /// </summary>
        /// <param name="dllName">The dll where the COM class is implemented</param>
        /// <param name="filterPersistClass">The requested Class ID</param>
        /// <returns>IClassFactory instance used to create instances of that class</returns>
        internal static IClassFactory GetClassFactory(string dllName, string filterPersistClass)
        {
            // Load the class factory from the dll
            IClassFactory classFactory = GetClassFactoryFromDll(dllName, filterPersistClass);
            return classFactory;
        }

        /// <summary>
        /// Gets the class factory from given dll
        /// </summary>
        /// <param name="dllName">Name of dll</param>
        /// <param name="filterPersistClass">ID of persisted class</param>
        /// <returns>Class factory object</returns>
        private static IClassFactory GetClassFactoryFromDll(string dllName, string filterPersistClass)
        {
            try
            {
                // Load the dll
                IntPtr dllHandle = Win32NativeMethods.LoadLibrary(dllName);
                if (dllHandle == IntPtr.Zero)
                {
                    return null;
                }

                // Keep a reference to the dll until the process\AppDomain dies
                dllList.AddDllHandle(dllHandle);

                // Get a pointer to the DllGetClassObject function
                IntPtr dllGetClassObjectPtr = Win32NativeMethods.GetProcAddress(dllHandle, "DllGetClassObject");

                if (dllGetClassObjectPtr == IntPtr.Zero)
                {
                    return null;
                }

                // Convert the function pointer to a .net delegate
                DllGetClassObject dllGetClassObject = (DllGetClassObject)Marshal.GetDelegateForFunctionPointer(dllGetClassObjectPtr, typeof(DllGetClassObject));

                // Call the DllGetClassObject to retreive a class factory for out Filter class
                Guid filterPersistGUID = new Guid(filterPersistClass);
                Guid classFactoryGUID = new Guid("00000001-0000-0000-C000-000000000046");
                object classFactory;

                if (dllGetClassObject(ref filterPersistGUID, ref classFactoryGUID, out classFactory) != 0)
                {
                    return null;
                }

                // Cast the returned object to IClassFactory
                return classFactory as IClassFactory;
            }
            catch (Exception ex)
            {
                Logger.LogError(ex, "Error occured in GetClassFactoryFromDll", (int)TraceLogEventId.GetClassFactoryFromDll);
                return null;
            }
        }

        /// <summary>
        /// Contains definitions for native methods
        /// </summary>
        private class Win32NativeMethods
        {
            /// <summary>
            /// Retrieves the address of an exported function or variable from the specified dynamic-link library (DLL).
            /// </summary>
            /// <param name="hModule">
            /// A handle to the DLL module that contains the function or variable. The LoadLibrary, LoadLibraryEx, or GetModuleHandle function returns this handle.
            /// The GetProcAddress function does not retrieve addresses from modules that were loaded using the LOAD_LIBRARY_AS_DATAFILE flag.
            /// For more information, see LoadLibraryEx.
            /// </param>
            /// <param name="lpProcName">
            /// The function or variable name, or the function's ordinal value. If this parameter is an ordinal value,
            /// it must be in the low-order word; the high-order word must be zero.
            /// </param>
            /// <returns>If the function succeeds, the return value is the address of the exported function or variable.</returns>
            [DllImport("kernel32.dll", CharSet = CharSet.Ansi)]
            public static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);

            /// <summary>
            /// Frees the loaded dynamic-link library (DLL) module and, if necessary, decrements its reference count.
            /// When the reference count reaches zero, the module is unloaded from the address space of the calling process and the handle is no longer valid.
            /// </summary>
            /// <param name="hModule">A handle to the loaded library module. The LoadLibrary, LoadLibraryEx, GetModuleHandle, or GetModuleHandleEx function returns this handle.</param>
            /// <returns>If the function succeeds, the return value is nonzero.</returns>
            [DllImport("kernel32.dll")]
            public static extern bool FreeLibrary(IntPtr hModule);

            /// <summary>
            /// Loads the specified module into the address space of the calling process. The specified module may cause other modules to be loaded.
            /// </summary>
            /// <param name="lpFileName">The name of the module. This can be either a library module (a .dll file) or an executable module (an .exe file).</param>
            /// <returns>If the function succeeds, the return value is a handle to the module.</returns>
            [DllImport("kernel32.dll")]
            public static extern IntPtr LoadLibrary(string lpFileName);
        }

        /// <summary>
        /// Holds a list of dll handles and unloads the dlls 
        /// in the destructor
        /// </summary>
        private class DllList
        {
            /// <summary>
            /// Internal list of loaded dlls
            /// </summary>
            private List<IntPtr> dllList = new List<IntPtr>();

            /// <summary>
            /// Finalizes an instance of the DllList class
            /// </summary>
            ~DllList()
            {
                foreach (IntPtr dllHandle in this.dllList)
                {
                    try
                    {
                        Win32NativeMethods.FreeLibrary(dllHandle);
                    }
                    catch
                    {
                    }
                }
            }

            /// <summary>
            /// Add dll handle to internal list
            /// </summary>
            /// <param name="dllHandle">Handle of dll</param>
            public void AddDllHandle(IntPtr dllHandle)
            {
                lock (this.dllList)
                {
                    this.dllList.Add(dllHandle);
                }
            }
        }
    }
}
